/*
	The MIT License

	Copyright (c) 2008 IFMO/GameDev Studio

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	THE SOFTWARE.
*/


#include "maya_local.h"

/*-----------------------------------------------------------------------------
	Maya export lib - parent class for level and object exportes 
-----------------------------------------------------------------------------*/


EMExportLib::EMExportLib( void )
{

}

EMExportLib::~EMExportLib( void )
{

}
//
//	GetTransformMatricies	
//	see: MFnTransform for details :	
//
static void GetTransformMatricies	( MObject &obj, 
									 MMatrix	&Sp,	MMatrix &St,	MMatrix &S,		MMatrix	&Sh,
									 MMatrix &Rp,	MMatrix	&Rt,	MMatrix	&Ro,	MMatrix &R,		MMatrix	&T	)
{																					
	MFnTransform	transform(obj);

	Sp	=	MMatrix::identity;
	St	=	MMatrix::identity;
	S	=	MMatrix::identity;
	Sh	=	MMatrix::identity;
	Rp	=	MMatrix::identity;
	Rt	=	MMatrix::identity;
	Ro	=	MMatrix::identity;
	R	=	MMatrix::identity;
	T	=	MMatrix::identity;

	MPoint		sp;		sp	=	transform.scalePivot( MSpace::kTransform );
	MVector		s;				transform.getScale( &s.x );
	MVector		sh;				transform.getShear( &sh.x );
	MPoint		spt;	spt	=	transform.scalePivotTranslation( MSpace::kTransform );
	MPoint		rp;		rp	=	transform.rotatePivot( MSpace::kTransform );
	MQuaternion	ro;		ro	=	transform.rotateOrientation( MSpace::kTransform );
	MQuaternion	r;				transform.getRotation( r, MSpace::kTransform );
	MPoint		rpt;	rpt	=	transform.rotatePivotTranslation( MSpace::kTransform );
	MVector		t;		t	=	transform.getTranslation( MSpace::kTransform );
	MTransformationMatrix::RotationOrder	rot_order;
	rot_order	=	 transform.rotationOrder();


	Sp(3,0)	=	sp.x / sp.w;		St(3,0)	=	spt.x / spt.w;
	Sp(3,1)	=	sp.y / sp.w;		St(3,1)	=	spt.y / spt.w;
	Sp(3,2)	=	sp.z / sp.w;		St(3,2)	=	spt.z / spt.w;

	S(0,0)	=	s.x;				Sh(1,0)	=	sh.x;
	S(1,1)	=	s.y;				Sh(2,0)	=	sh.y;
	S(2,2)	=	s.z;				Sh(2,1)	=	sh.z;

	Rp(3,0)	=	rp.x / rp.w;		Rt(3,0)	=	rpt.x / rpt.w;
	Rp(3,1)	=	rp.y / rp.w;		Rt(3,1)	=	rpt.y / rpt.w;
	Rp(3,2)	=	rp.z / rp.w;		Rt(3,2)	=	rpt.z / rpt.w;

	Ro	=	ro.asMatrix();
	R	=	r.asMatrix();

	T(3,0)	=	t.x;
	T(3,1)	=	t.y;
	T(3,2)	=	t.z;
}	


//
//	GetParentDagPathIndex
//
static uint GetParentDagPathIndex( const MDagPathArray &dagPathArray, const MDagPath &child )
{
	MDagPath	parent	=	child;
	parent.pop();
	
	for (uint i=0; i<dagPathArray.length(); i++) {
		if ( dagPathArray[i] == parent ) {
			return i;
		}
	}
	return -1;
}


//
//	EMLevelExporter::ParseJoints
//
MStatus EMExportLib::ParseJoints( IPxTriMesh mesh, MObject &root_joint, MDagPath &mesh_path )
{
	MFnTransform mesh_transform(mesh_path.transform());
	MVector mesh_pos = mesh_transform.getTranslation( MSpace::kTransform );

	//	
	//	get joint's dag paths :
	//
	MItDag dag_iterator;
	dag_iterator.reset( root_joint, MItDag::kBreadthFirst, MFn::kJoint );
	MDagPathArray	dagPathArray;

	for ( ; !dag_iterator.isDone(); dag_iterator.next() ) {
		MDagPath path;
		dag_iterator.getPath( path );
		dagPathArray.append( path );
	}
	
	//
	//	iterate joints :
	//	
	for ( uint i=0; i<dagPathArray.length(); i++ )
	{
		MDagPath dagPath	=	dagPathArray[i];
		
		MObject joint_obj	=	dagPath.node();
		// Joint staff
		MFnDagNode			dag_joint( joint_obj );
		MFnTransform		transf( joint_obj );
		MFnIkJoint			joint( joint_obj );
		MQuaternion			orient;
		MQuaternion			rotation;
		MQuaternion			scale;
		MVector				vec;
		
		joint.getOrientation( orient );
		joint.getScaleOrientation( scale );
		transf.getRotation( rotation );

		rotation.normalizeIt();
		orient.normalizeIt();

		vec = transf.getTranslation( MSpace::kTransform );

		EJoint joint_add;
		joint_add.name         = dagPath.partialPathName().asChar();	
		joint_add.orient       = EQuaternion( orient.x, orient.y, orient.z, orient.w );
		joint_add.def_orient   = EQuaternion( orient.x, orient.y, orient.z, orient.w );
		joint_add.position	   = EPoint( vec.x, vec.y, vec.z );
		joint_add.def_position = EPoint( vec.x, vec.y, vec.z );

		//Searching parent
		if ( mesh->GetJointNum() == 0 ) {
		
			joint_add.parent_index	=	-1;
			joint_add.bind_pos		=	EPoint( vec.x - mesh_pos.x, vec.y - mesh_pos.y, vec.z - mesh_pos.z );
			joint_add.def_position  =	EPoint( vec.x - mesh_pos.x, vec.y - mesh_pos.y, vec.z - mesh_pos.z );
			joint_add.bind_orient   =	EQuaternion( rotation.x, rotation.y, rotation.z, rotation.w );
			
		} else {

			joint_add.parent_index	=	GetParentDagPathIndex( dagPathArray, dagPath );
		
			EQuaternion parent_orient	=	mesh->GetJoint( joint_add.parent_index )->bind_orient;
			EPoint parent_pos			=	mesh->GetJoint( joint_add.parent_index )->bind_pos;
			joint_add.bind_orient		=	( parent_orient * joint_add.def_orient ).Normalize();
			joint_add.bind_pos			=	parent_pos +  EVector( joint_add.def_position.Rotate( parent_orient ) );
			
		}
		
		// Joint Constraints
		if(joint.isLimited(MFnTransform::kRotateMinX))	joint_add.angular_limit_min.x = (float) joint.limitValue(MFnTransform::kRotateMinX);
		if(joint.isLimited(MFnTransform::kRotateMinY))	joint_add.angular_limit_min.y = (float) joint.limitValue(MFnTransform::kRotateMinY);
		if(joint.isLimited(MFnTransform::kRotateMinZ))	joint_add.angular_limit_min.z = (float) joint.limitValue(MFnTransform::kRotateMinZ);
		if(joint.isLimited(MFnTransform::kRotateMaxX))	joint_add.angular_limit_max.x = (float) joint.limitValue(MFnTransform::kRotateMaxX);
		if(joint.isLimited(MFnTransform::kRotateMaxY))	joint_add.angular_limit_max.y = (float) joint.limitValue(MFnTransform::kRotateMaxY);
		if(joint.isLimited(MFnTransform::kRotateMaxZ))	joint_add.angular_limit_max.z = (float) joint.limitValue(MFnTransform::kRotateMaxZ);

		mesh->AddJoint( joint_add );
	}
	
	return MS::kSuccess;
}
/*-----------------------------------------------------------------------------
	Mesh :
-----------------------------------------------------------------------------*/

static MStatus GetLocalVertexIndex(MIntArray poly_vert_inds, int global_vert_index, int &local_vert_index) {
	for (uint i=0; i<poly_vert_inds.length(); i++) {
		if (poly_vert_inds[i]==global_vert_index) {
			local_vert_index = i;
			return MS::kSuccess;
		}
	}
	return MS::kFailure;
}

//
//	EMLevelExporter::ParseMesh
//
MStatus EMExportLib::ParseMesh( IPxTriMesh lv_mesh, MObject &obj, MDagPath &path )
{
	LOGF("Parsing mesh : %s", path.fullPathName().asChar());

	MStatus	status;

	MFnMesh				mesh(obj);
	MString				uv_set_name;
	MString				clr_set_name;
	bool				has_color;
	MPointArray			vertex_array;
	MColorArray			color_array;
	MFloatVectorArray	normal_array;
	//MFloatVectorArray	tangent_array;
	//MFloatVectorArray	binormal_array;
	MFloatArray			u_array;
	MFloatArray			v_array;

	MPlugArray			plug_array;
	MFnSkinCluster*		skin = NULL;
	std::vector<MFloatArray>	weights_array;
	std::vector<MIntArray>	weights_indexes;
	MIntArray			joint_indexes;
	MDagPathArray		joints_array;

#if 0 // Use the code below just if you not delete Non-Deformable History
	mesh.getConnections( plug_array );	
	for ( uint i = 0; i < plug_array.length(); i++ )
	{
		if ( plug_array[i].partialName(false, false, false, false, false, true) == "inMesh" ) 
		{
			//LOGF("Parsing plug array :  %s", plug_array[i].name() );
			MPlug      cur_plag = plug_array[i];
			MPlugArray connected_plugs;

			bool has_con = cur_plag.connectedTo( connected_plugs, true, true );

			for ( uint j = 0; j < connected_plugs.length(); j++ )
			{
				MObject plug_object = connected_plugs[j].node();

				if ( plug_object.hasFn( MFn::kSkinClusterFilter ) )
				{
					skin = new MFnSkinCluster( plug_object );
					LOGF("Parsing SkinCluster : %s", skin->name() );
					break;
				}
			}
			break;
		}
	}
	
#else
	MItDependencyNodes dep_node_iter( MFn::kSkinClusterFilter );

	for (; !dep_node_iter.isDone() && !skin; dep_node_iter.next())
	{
		MObject object_from_dep = dep_node_iter.item();
				skin			= new MFnSkinCluster(object_from_dep);
		uint	geometry_num	= skin->numOutputConnections();

		for(uint geometry_index = 0; geometry_index < geometry_num; geometry_index++ ) 
		{
			uint connection_index = skin->indexForOutputConnection(geometry_index);
			MObject output_object = skin->outputShapeAtIndex(connection_index);

			if(output_object != mesh.object()) {
				delete skin;
				skin = NULL;
			}
		}
	}
#endif

	if ( skin )
	{
		MItGeometry iter_geom( path );
		uint num_components = 0;

		uint infl_obj_num = skin->influenceObjects( joints_array );
		if (infl_obj_num > 0)
		{
			MDagPath root_path = joints_array[infl_obj_num - 1];
			while (root_path.length() > 1)
			{
				root_path.pop();
			}
			LOGF("Parsing Joint : %s", root_path.partialPathName());
			MSelectionList selection_list;
			MGlobal::getActiveSelectionList(selection_list);
			MGlobal::selectByName(root_path.fullPathName(),MGlobal::kReplaceList);
			MGlobal::executeCommand("gotoBindPose()", true);
			MGlobal::setActiveSelectionList(selection_list, MGlobal::kReplaceList);
			ParseJoints(lv_mesh, root_path.node(), path);
		}

		weights_array.resize( mesh.numVertices() );
		weights_indexes.resize( mesh.numVertices() );

		for ( ; !iter_geom.isDone(); iter_geom.next() )
		{
			MObject mesh_component = iter_geom.currentItem();
			skin->getWeights(path, mesh_component, weights_array[ iter_geom.index() ], num_components);
		}

		for ( uint i = 0; i < weights_array.size(); i++)
		{
			MFloatArray max_weights( 4, 0.0f );
			MIntArray   max_indexes( 4, 0 );
			float		rest = 0.0f;;

			// looking for four maximal element of the array
			for ( uint j = 0; j < weights_array[i].length(); j++ )
			{
				for (uint k = 0; k < 4; k++ ) 
				{
					if ( weights_array[i][j] > max_weights[k] && k != 3 )
					{
						continue;
					}
					else if( weights_array[i][j] > max_weights[k] && k == 3 )
					{
						for (uint ind = 0; ind < k; ind++)
						{
							max_weights[ind] = max_weights[ind + 1];
							max_indexes[ind] = max_indexes[ind + 1];
						}
						max_weights[k] = weights_array[i][j];
						max_indexes[k] = j;
						break;
					}
					else if ( k != 0 )
					{
						for (uint ind = 0; ind < (k - 1); ind++)
						{
							max_weights[ind] = max_weights[ind + 1];
							max_indexes[ind] = max_indexes[ind + 1];
						}
						max_weights[k - 1] = weights_array[i][j];
						max_indexes[k - 1] = j;
						break;
					}
				}
			}

			float sum = max_weights[0] + max_weights[1] + max_weights[2] + max_weights[3];
			rest = (1.0f - sum )/ 4.0f;

			if ( rest < 0 )
			{
				LOGF("o0 WTF? %d", i );
			}

			max_weights[0] += rest;
			max_weights[1] += rest;
			max_weights[2] += rest;
			max_weights[3] += rest;

			weights_array[i]   = max_weights;
			weights_indexes[i] = max_indexes;
		}

		uint influence_obj_num = skin->influenceObjects( joints_array );
		joint_indexes.setLength( influence_obj_num );

		for ( uint i = 0; i < influence_obj_num; i++ )
		{
			for ( uint j = 0; j < lv_mesh->GetJointNum(); j++ )
			{
				EJoint *joint = lv_mesh->GetJoint( j );
				MString joint_name = joint->name.CStr();
				if ( joint_name == joints_array[i].partialPathName() )
				{
					joint_indexes[i] = j;
					break;
				}
			}
		}
	
		for ( uint i = 0; i < weights_indexes.size(); i++ )
		{
			for ( uint j = 0; j < weights_indexes[i].length(); j++ )
			{
				weights_indexes[i][j] = joint_indexes[ weights_indexes[i][j] ];
			}
		}

	}

	//
	//	Get Color and UV set names :
	//
	MAYA_CALL( mesh.getCurrentUVSetName(uv_set_name) );
	MAYA_CALL( mesh.getCurrentColorSetName(clr_set_name) );

	//	NOTE : UV set is always presented, but color is not.
	if (clr_set_name!="") {
		MAYA_CALL( mesh.getFaceVertexColors(color_array, &clr_set_name, &MColor(1.0f, 1.0f, 1.0f, 1.0f) ) );
		has_color = true;
	} else {
		has_color = false;
	}
	
	//
	//	Get positions, normals, tangents, etc :
	//
	MAYA_CALL( mesh.getPoints( vertex_array, MSpace::kObject ) );
	MAYA_CALL( mesh.getNormals( normal_array, MSpace::kObject ) );
	//MAYA_CALL( mesh.getTangents( tangent_array, MSpace::kObject, &uv_set_name ) );
	//MAYA_CALL( mesh.getBinormals( binormal_array, MSpace::kObject, &uv_set_name ) );
	MAYA_CALL( mesh.getUVs(u_array, v_array, &uv_set_name) );
	
	//
	//	Get number of instanced copy :
	//
	MAYA_CALL( path.extendToShape() );
	
	int instance_num = 0;
	if (path.isInstanced()) {
		instance_num = path.instanceNumber();
	}

	//
	//	Get connected shaders :	
	//
	MObjectArray	shaders;
	MIntArray		face_shader_inds;
	MAYA_CALL( mesh.getConnectedShaders(instance_num, shaders, face_shader_inds) );
		//LOGF("   Mesh has %d faces and %d shaders\r\n", face_shader_inds.length(), shaders.length());
		//for (uint i=0; i<face_shader_inds.length(); i++) {
		//	LOGF(" %d ", face_shader_inds[i]);
		//}
		//LOGF("\r\n");
	
	MStringArray	texture_names;
	texture_names.setLength( shaders.length() );
	
	for (uint i=0; i<shaders.length(); i++) {
		MAYA_CALL( ParseConnectedShader(shaders[i], texture_names[i]) );
		//LOGF("   Texture : %s\r\n", texture_names[i].asChar());
	}
	
	//
	//	Get faces :
	//
	MItMeshPolygon	poly_iter(obj, &status);
	MAYA_CALL(status);
	//	For each face :
	for ( poly_iter.reset();  !poly_iter.isDone();  poly_iter.next()) {

		uint face_id = poly_iter.index();
				
		int num_tris = 0;
		MAYA_CALL( poly_iter.numTriangles(num_tris) );
		
		MPointArray	points;
		MIntArray	tri_vertex_list;
		MIntArray	poly_vertex_list;

		MAYA_CALL( poly_iter.getVertices( poly_vertex_list ) );
		
		//	Attach shading group :
		EShadingGroup	sg;
		sg.start	=	lv_mesh->GetTriangleNum();
		sg.num		=	num_tris;
		sg.shader	=	texture_names[ face_shader_inds[ face_id ] ].asChar();
		lv_mesh->AttachSG( sg );

		//	For each triangle in face :		
		for (uint j=0; j<(uint)num_tris; j++) {
			
			MAYA_CALL( poly_iter.getTriangle(j, points, tri_vertex_list) );
			
			uint tri_inds[3];

			//	For each triangle vertex :			
			for (uint k=0; k<3; k++) {
				EVertex	vertex;
				MVector	n;
				MColor	c(1,0,0,1);
				float u=0, v=0;
				int local_index;
				
				MAYA_CALL( GetLocalVertexIndex(poly_vertex_list, tri_vertex_list[k], local_index) );
				
				vertex.position.x	=	points[k].x / points[k].w;
				vertex.position.y	=	points[k].y / points[k].w;
				vertex.position.z	=	points[k].z / points[k].w;

				if (skin)
				{
					for ( uint ind = 0; ind < 4; ind++ )
					{
						vertex.joint_weights[ind] = weights_array[ tri_vertex_list[k] ][ind];
						vertex.joint_inds[ind]    = weights_indexes[ tri_vertex_list[k] ][ind];
					}
				}
				
				MAYA_CALL( mesh.getFaceVertexNormal(face_id, tri_vertex_list[k], n) );
				//MAYA_CALL( mesh.getPolygonUV(face_id, local_index, u, v, &uv_set_name) );
				if ( mesh.getPolygonUV( face_id, local_index, u, v, &uv_set_name) != MS::kSuccess ) {
					MGlobal::displayWarning("getPolygonUV() failed. Probably polygons does not have attached UV-sets\r\n");
					return MS::kFailure;
					//	TODO : do somehting with polys that does not have uv-set.
				}
				
				if (has_color) {				
					MAYA_CALL( poly_iter.getColor(c, local_index) );
				}

				vertex.normal		=	EVector(n.x, n.y, n.z);
				vertex.uv0			=	ETexCoord( u, -v );
				vertex.color0		=	EColor(c.r, c.g, c.b, c.a);
				
				tri_inds[k] = lv_mesh->AddVertex( vertex );
			}
			
			lv_mesh->AddTriangle(tri_inds[0], tri_inds[1], tri_inds[2]);
		}
	}

	lv_mesh->MergeVertices();	
	lv_mesh->GlueSGs();	
	
	if ( skin )
	{
		delete skin;
	}
	return MStatus::kSuccess;
}

//
//	EMLevelExporter::ParseConnectedShader
//	TODO : if no shader connected, make virtual texture like "*colorFF8040FF"
//
MStatus EMExportLib::ParseConnectedShader(MObject &set, MString &texture_name)
{
	MStatus	status;

	MObject shader_node = FindShader(set);
	if (MObject::kNullObj == shader_node) {
		return MS::kSuccess;	//	just no shader, so it is not an error.
	}


	MPlug color_plug = MFnDependencyNode(shader_node).findPlug("color", &status);
	MAYA_CALL(status);
	
	
	MItDependencyGraph itDG(color_plug, MFn::kFileTexture,
							MItDependencyGraph::kUpstream, 
							MItDependencyGraph::kBreadthFirst,
							MItDependencyGraph::kNodeLevel, 
							&status);
	MAYA_CALL(status);

	//	disable automatic pruning so that we can locate a specific plug 
	itDG.disablePruningOnFilter();

	//	If no texture file it means, 
	//	that no textures are connected to color,
	//	so set texture name to virtual texture name.
	if (itDG.isDone()) {
	
		if (color_plug.isCompound()) {
			float fr,fg,fb;
			ASSERT(color_plug.numChildren()==3);
			color_plug.child(0).getValue(fr);
			color_plug.child(1).getValue(fg);
			color_plug.child(2).getValue(fb);
			unsigned char r = fr*255;
			unsigned char g = fg*255;
			unsigned char b = fb*255;
			texture_name = va("*color%02X%02X%02XFF", r, g, b);
		}
	
	} else {
		MObject textureNode = itDG.thisNode();
		MString fullpath;
		MString projdir = Maya_ProjectDir();
		
		MPlug filenamePlug = MFnDependencyNode(textureNode).findPlug("fileTextureName");
		filenamePlug.getValue(fullpath);
		
		//	strip out full path :
		texture_name = fullpath.substring( projdir.length()+1, fullpath.length() );
	}	
	
	return MS::kSuccess;	
}


//
//	EMLevelExporter::FindShader
//
//	Summary	:	finds the shading node for the given shading group set node
//	Args    :	setNode - the shading group set node
//	Returns :	the shader node for setNode if found;
//				MObject::kNullObj otherwise
//
MObject EMExportLib::FindShader(const MObject& set) 
{

	MFnDependencyNode fn_node(set);
	MPlug shader_plug = fn_node.findPlug("surfaceShader");
			
	if (!shader_plug.isNull()) 
	{			
		//	buffer for all plug, 
		//	that are connected as destination of current set node :
		MPlugArray connected_plugs;	

		//	get all the plugs that are connected as the destination of this 
		//	surfaceShader plug so we can find the surface shaderNode
		MStatus status;
		shader_plug.connectedTo(connected_plugs, true, false, &status);
		
		if (MStatus::kFailure == status) {
			MGlobal::displayError("MPlug::connectedTo");
			return MObject::kNullObj;
		}

		if (1 != connected_plugs.length()) {
			MGlobal::displayError("Error getting shader");
		} else {
			return connected_plugs[0].node();
		}
	}
	
	return MObject::kNullObj;
}


